#pragma once
#ifndef SIMPLEMATRIX
#define SIMPLEMATRIX

#include <iostream>
#include <vector>
#include "time.h"
#include <cstdlib>

using namespace std;

template <class A>
class SimpleMatrix
{
public:
	SimpleMatrix(int matrixRows, int matrixColumns, A init)
	{
		rows = (matrixRows > 0 ? matrixRows : 10);
		columns = (matrixColumns > 0 ? matrixColumns : 10);

		matrix.resize(rows * columns);

		for (int i = 0; i < matrix.size(); i++)
			matrix[i] = init;
	}

	SimpleMatrix(int matrixRows, int matrixColumns)
	{
		rows = (matrixRows > 0 ? matrixRows : 10);
		columns = (matrixColumns > 0 ? matrixColumns : 10);

		matrix.resize(rows * columns);
	}
	
	SimpleMatrix(A *elements, int length, int matrixColumns)
	{
		rows = length / matrixColumns;
		columns = matrixColumns;
		matrix.resize(rows * columns);

		for (int i = 0; i < length; i++)
			matrix[i] = elements[i];
	}

	~SimpleMatrix() 
	{
		matrix.clear();
	}

	void print()
	{
		for (int i = 0; i < matrix.size(); i++)
		{
			if (i % columns == 0)
				cout << "\n";

			cout << matrix[i] << (sizeof(A) == sizeof(char) || sizeof(A) == sizeof(unsigned char) ? " " : "\t");
		}

		cout << endl;
	}

	void printIndices()
	{
		for (int i = 0; i < matrix.size(); i++)
		{
			if (i % columns == 0)
				cout << "\n";

			cout << (int)(matrix[i]) << (sizeof(A) == sizeof(char) || sizeof(A) == sizeof(unsigned char) ? " " : "\t");
		}

		cout << endl;
	}

	
	void print(vector<char> symbols, vector<A> elements)
	{
		for (int i = 0; i < matrix.size(); i++)
		{
			if (i % columns == 0)
				cout << "\n";
			int elementIndex = findIndex(elements, matrix[i]);
			if (elementIndex == -1)
				cout << matrix[i] << (sizeof(A) == sizeof(char) || sizeof(A) == sizeof(unsigned char) ? " " : "\t");
			else
				cout << symbols[elementIndex] << " ";
		}

		cout << endl;
	}

	void randomize()
	{
		srand(time(0));

		for (int i = 0; i < rows; i++)
			for (int j = 0; j < columns; j++)
				set(i, j, (A)(rand() % 50));
	}

	
	int height()
	{
		return rows;
	}

	int width()
	{
		return columns;
	}

	void set(int rowIndex, int columnIndex, A value)
	{
		if (rowIndex < 0 || rowIndex >= rows)
		{
			cerr << "\nError in SET row: " << rowIndex << " out of range. Max rows: " << rows;
			exit(1);
		}

		if (columnIndex < 0 || columnIndex >= columns)
		{
			cerr << "\nError in SET column: " << columnIndex << " out of range. Max columns: " << columns;
			exit(1);
		}

		matrix[rowIndex * columns + columnIndex] = value;
	}

	A get(int rowIndex, int columnIndex)
	{
		if (rowIndex < 0 || rowIndex >= rows)
		{
			cerr << "\nError in GET row: " << rowIndex << " out of range. Max rows: " << rows;
			exit(1);
		}

		if (columnIndex < 0 || columnIndex >= columns)
		{
			cerr << "\nError in GET column: " << columnIndex << " out of range. Max columns: " << columns;
			exit(1);
		}

		return matrix[rowIndex * columns + columnIndex];
	};

	vector<A> buffer()
	{
		return matrix;
	}

	vector<A> getRow(int rowIndex)
	{
		if (rowIndex < 0 || rowIndex >= rows)
		{
			cerr << "\nError row: " << rowIndex << " out of range. Max rows: " << rows;
			exit(1);
		}

		vector<A> row(columns);

		for (int i = 0; i < row.size(); i++)
			row[i] = get(rowIndex, i);

		return row;
	}

	void setRow(vector<A> elements, int rowIndex)
	{
		if (rowIndex < 0 || rowIndex >= rows)
		{
			cerr << "\nError row: " << rowIndex << " out of range. Max rows: " << rows;
			exit(1);
		}

		for (int j = 0; j < columns; j++)
			set(rowIndex, j, elements[j]);
	}

	vector<A> getColumn(int columnIndex)
	{
		if (columnIndex < 0 || columnIndex >= columns)
		{
			cerr << "\nError column: " << columnIndex << " out of range. Max columns: " << columns;
			exit(1);
		}

		vector<A> column(rows);
		
		for (int i = 0; i < column.size(); i++)
			column[i] = get(i, columnIndex);

		return column;
	}

	SimpleMatrix<A>* rowSubset(int rowIndex, int offset)
	{
		
		if (rowIndex < 0 || rowIndex >= rows)
		{
			cerr << "\nError start row: " << rowIndex<< " out of range. Max rows: " << rows;
			exit(1);
		}

		if (offset <= 0)
		{
			cerr <<"\nError: the offset must be greater than 0";
			exit(1);
		}

		if (rowIndex + offset >= rows)
		{
			cerr << "\nError: the offset " << offset << " is too large";
			exit(1);
		}

		SimpleMatrix<A>* subset = new SimpleMatrix<A>(offset, columns);

		for (int i = rowIndex, k = 0; i < rowIndex + offset; i++, k++)
			for (int j = 0; j < columns; j++)
				subset -> set(k, j, get(i, j));

		return subset;
	}

	SimpleMatrix<A>* columnSubset(int columnIndex, int offset)
	{
		
		if (columnIndex < 0 || columnIndex >= columns)
		{
			cerr << "\nError start column: " << columnIndex<< " out of range. Max columnss: " << columns;
			exit(1);
		}

		if (offset <= 0)
		{
			cerr <<"\nError: the offset must be greater than 0";
			exit(1);
		}

		if (columnIndex + offset >= columns)
		{
			cerr << "\nError: the offset " << offset << " is too large";
			exit(1);
		}

		SimpleMatrix<A>* subset = new SimpleMatrix<A>(rows, offset);

		for (int j = columnIndex, k = 0; j < columnIndex + offset; j++, k++)
			for (int i = 0; i < rows; i++)
				subset -> set(i, k, get(i, j));

		return subset;
	}


private:
	int rows;
	int columns;
	vector<A> matrix;

	int findIndex(vector<A> list, A element)
	{
		for (int i = 0; i < list.size(); i++)
			if (list[i] == element)
				return i;

		return -1;
	}

};

#endif